﻿// This code is released under the BSD license.
namespace PugLib.Cryptography
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;

    public sealed class SaltedHash
    {
        private const int saltLength = 32;
        private readonly string _hash;
        private readonly string _salt;

        public string Salt
        {
            get { return _salt; }
        }

        public string Hash
        {
            get { return _hash; }
        }

        public static SaltedHash Create(string password)
        {
            string salt = _createSalt();
            string hash = _calculateHash(salt, password);
            return new SaltedHash(salt, hash);
        }

        public static SaltedHash Create(string salt, string hash)
        {
            return new SaltedHash(salt, hash);
        }

        public static SaltedHash CreateWithSalt(string salt, string password)
        {
            return new SaltedHash(salt, _calculateHash(salt, password));
        }

        public static bool Verify(string salt, string password, string hash)
        {
            return (0 == string.CompareOrdinal(hash, CreateWithSalt(salt, password).Hash));
        }

        public bool Verify(string password)
        {
            return _hash.Equals(_calculateHash(_salt, password));
        }

        public static string GetHashFromFile(string fileName)
        {
            if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName))
            {
                byte[] fileBytes = File.ReadAllBytes(fileName);
                return Convert.ToBase64String(_calculateHash(fileBytes));
            }
            return string.Empty;
        }

        public static string GetHashFromStream(Stream fileStream)
        {
            if (fileStream != null && fileStream.CanRead)
            {
                return Convert.ToBase64String(_calculateHash(fileStream));
            }
            return string.Empty;
        }

        #region private

        private SaltedHash(string s, string h)
        {
            _salt = s;
            _hash = h;
        }

        private static string _createSalt()
        {
            byte[] r = _createRandomBytes(saltLength);
            return Convert.ToBase64String(r);
        }

        private static byte[] _createRandomBytes(int len)
        {
            byte[] r = new byte[len];
            new RNGCryptoServiceProvider().GetBytes(r);
            return r;
        }

        private static string _calculateHash(string salt, string password)
        {
            byte[] data = _toByteArray(salt + password);
            byte[] hash = _calculateHash(data);
            return Convert.ToBase64String(hash);
        }

        private static byte[] _calculateHash(byte[] data)
        {
            using (SHA256CryptoServiceProvider foo = new SHA256CryptoServiceProvider())
            {
                return foo.ComputeHash(data);
            }
        }


        private static byte[] _calculateHash(Stream data)
        {
            using (SHA256CryptoServiceProvider foo = new SHA256CryptoServiceProvider())
            {
                return foo.ComputeHash(data);
            }
        }

        private static byte[] _toByteArray(string s)
        {
            return Encoding.UTF8.GetBytes(s);
        }

        #endregion
    }
}